﻿using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace ResourceEnumerator.Inrastructure
{
    /// <summary>
    /// Auto-complete behaviour for ComboBoxes
    /// This behaviour makes the following assumptions:
    /// 1. IsEditable property would be set to true on target combo box
    /// 2. IsTextSearchEnabled property would be set to false
    /// 3. Relies on ToString() method to get string representation of an item in ItemSource/Items
    /// </summary>
    public static class AutoCompleteBehavior
    {
        /// <summary>
        /// Set this property to true, to get auto-complete behaviour
        /// </summary>
        public static readonly DependencyProperty IsAutoCompleteEnabledProperty =
            DependencyProperty.RegisterAttached("IsAutoCompleteEnabled", typeof (bool), typeof (AutoCompleteBehavior),
                                        new FrameworkPropertyMetadata(default(bool), IsAutoCompleteEnabledChanged));

        /// <summary>
        /// This property is internally used by this class - 
        /// if you set it from XAML, Items will be filtered based on that text
        /// Made access of this property to public, so that it can be used by Designers (VS/Blend)
        /// Intellisense wont work on this property coz of EditorBrowsable property
        ///  </summary>
        [EditorBrowsable(EditorBrowsableState.Never)] 
        public static readonly DependencyProperty FilterTextProperty =
            DependencyProperty.RegisterAttached("FilterText", typeof (string), typeof (AutoCompleteBehavior),
                                                new FrameworkPropertyMetadata(null, FilterTextChanged));

        #region [ IsAutoCompleteEnabled Stuff ]

        private static void IsAutoCompleteEnabledChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var comboBox = dependencyObject as ComboBox;
            if (comboBox == null) return;
            if (!(bool) dependencyPropertyChangedEventArgs.NewValue) return;
            
            if (!comboBox.IsLoaded)
                comboBox.Loaded += CmBoxOnLoadedForIsAutoCompleted;
            else AttachHandlers(comboBox);
        }

        private static void CmBoxOnLoadedForIsAutoCompleted(object sender, RoutedEventArgs routedEventArgs)
        {
            AttachHandlers(sender as ComboBox);
        }

        private static void AttachHandlers(ComboBox comboBox)
        {
            if (comboBox != null)
            {
                comboBox.PreviewKeyDown += (sender, args) =>
                {
                    // something is going to be selected, close the popup
                    if (args.Key == Key.Tab || args.Key == Key.Enter)
                    {
                        comboBox.IsDropDownOpen = false;
                    }
                    // user pressed escape, close the popup
                    else if (args.Key == Key.Escape)
                    {
                        comboBox.IsDropDownOpen = false;
                        comboBox.Text = (string)comboBox.GetValue(FilterTextProperty);
                    }
                };

                comboBox.KeyUp += (sender, args) =>
                {
                    // eat up and down keys, as these keys _will_ change the dropdown behaviour
                    if (args.Key == Key.Up || args.Key == Key.Down)
                    {
                        // eat
                    }
                    // Something is selected, or losing focus
                    else if (args.Key == Key.Tab || args.Key == Key.Enter)
                    {
                        comboBox.SetValue(FilterTextProperty, string.Empty);
                    }
                    else
                    {
                        comboBox.SelectedIndex = -1;
                        comboBox.SetValue(FilterTextProperty, comboBox.Text);
                    }
                };
            }
        }

        public static bool GetIsAutoCompleteEnabled(DependencyObject depObject)
        {
            return (bool)depObject.GetValue(IsAutoCompleteEnabledProperty);
        }

        public static void SetIsAutoCompleteEnabled(DependencyObject depObject, bool value)
        {
            depObject.SetValue(IsAutoCompleteEnabledProperty, value);
        }

        #endregion

        #region [ FilterText Stuff ]

        private static void FilterTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var comboBox = dependencyObject as ComboBox;
            if (comboBox == null) return;
            var oldFilterText = (string) dependencyPropertyChangedEventArgs.OldValue;
            var newFilterText = (string) dependencyPropertyChangedEventArgs.NewValue;

            if (oldFilterText == newFilterText) return;

            if (!comboBox.IsLoaded)
                comboBox.Loaded += CmBoxOnLoadedForFilterText;
            else AttachFilterPredicate(comboBox);
        }

        private static void CmBoxOnLoadedForFilterText(object sender, RoutedEventArgs routedEventArgs)
        {
            var comboBox = sender as ComboBox;
            if (comboBox != null)
            {
                AttachFilterPredicate(comboBox);
            }
        }

        private static void AttachFilterPredicate(ComboBox comboBox)
        {
            if (comboBox == null) return;
            var filterText = (string) comboBox.GetValue(FilterTextProperty) ?? string.Empty;

            if (comboBox.Items != null)
            {
                comboBox.Items.Filter += o => o.ToString().ToLower().Contains(filterText.ToLower());
            }

            if (!string.IsNullOrEmpty(filterText))
                comboBox.IsDropDownOpen = true;

            var textBox = comboBox.Template.FindName("PART_EditableTextBox", comboBox) as TextBox;

            Debug.Assert(textBox != null);

            textBox.SelectionStart = int.MaxValue;
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public static string GetFilterText(DependencyObject depObject)
        {
            return (string) depObject.GetValue(FilterTextProperty);
        }

        [EditorBrowsable(EditorBrowsableState.Never)]
        public static void SetFilterText(DependencyObject depObject, string value)
        {
            depObject.SetValue(FilterTextProperty, value);
        }

        #endregion
    }
}
